//
//
//
//
//
// 启动鼠标事件
import { Object3D, Raycaster, Scene, Vector2, WebGLRenderer } from "three";
import { Camera, Intersection, Mesh, Renderer } from "three";
import { debounce } from "lodash";
import {
    computeBoundsTree,
    disposeBoundsTree,
    acceleratedRaycast,
} from "three-mesh-bvh";

// Add the extension functions
Mesh.prototype.raycast = acceleratedRaycast;

export interface ThreeMouseEvent extends Intersection {
    e: MouseEvent;
    renderer: Renderer;
    camera: Camera;
    type: string;
    offsetX: number;
    offsetY: number;
    x: number;
    y: number;
}

/**
 *  js 专用的鼠标拾取模型对象
 */
export default class ThreeMouse {
    constructor(renderer: WebGLRenderer, camera: Camera, scene: Scene) {
        this._renderer = renderer;
        this._camera = camera;
        this._scene = scene;
        this._frame = this._renderer.info.render.frame;

        this._renderer.domElement.addEventListener(
            "mouseenter",
            () => {
                this._mouseInRenderer = true;
            },
            true
        ); //鼠标进入开启拾取
        this._renderer.domElement.addEventListener(
            "mouseleave",
            this._mouseleave,
            true
        ); //鼠标离开停止拾取

        this._renderer.domElement.addEventListener(
            "mousemove",
            this._mousemove,
            true
        ); //鼠标移动监听事件
        this._renderer.domElement.addEventListener(
            "pointerdown",
            this._mousedown,
            true
        ); //鼠标按下监听事件

        this._renderer.domElement.addEventListener(
            "pointerup",
            this._mouseup,
            true
        ); //鼠标弹起监听事件
    }

    // 渲染器
    _renderer!: WebGLRenderer;

    _camera!: Camera;

    _scene!: Scene;

    // 鼠标在渲染器上面的位置
    _mosePosition = new Vector2(0, 0);

    // 鼠标按下的位置 如果和鼠标弹起的位置相同 才算一次有效的点击事件
    _mouseDownPosition = new Vector2(0, 0);

    //当前鼠标指针上的对象
    _mesh_on?: Object3D;

    //鼠标按下的对象
    _mesh_Down?: Object3D;

    //射线拾取器
    _raycaster = new Raycaster();

    // 当前渲染的帧数 ( 鼠标滑动事件 一帧只需要执行费一次即可 )
    _frame: number = 0;

    //鼠标是否在渲染器上
    _mouseInRenderer: boolean = false;

    // 鼠标滑动的事件
    _mousemoveEvent?: MouseEvent;

    // 鼠标事件
    _mouseEvent?: MouseEvent;

    // 需要被拾取的对象数组
    _object3ds: Object3D[] = [];

    // 点击次数
    _click_num = 0;

    // 获取拾取到的对象
    _getSelected(object3ds: Object3D[]): Intersection | undefined {
        let intersection: Intersection | undefined;

        if (object3ds.length) {
            intersection = this._raycaster.intersectObjects(object3ds, false)[0];
        }

        return intersection;
    }

    // 判断对象上面是否有事件
    hasEvent(
        object?: Object3D,
        types: Array<
            | "leftclick"
            | "rightclick"
            | "click"
            | "dbclick"
            | "mouseenter"
            | "mouseleave"
            | "mousemove"
            | "mousedown"
            | "mouseup"
        > = []
    ) {
        if (!object) {
            return false;
        }

        // 如果 启用事件为关闭的话就不执行事件
        if (object["userData"] && object["userData"]["enableEvent"] === false) {
            return false;
        }

        const hasevent = types.some((type) => {
            //@ts-ignore
            if (
                object["_listeners"] &&
                object["_listeners"][type] &&
                object["_listeners"][type][0]
            ) {
                return true;
            }
            // if (object["userData"] && object["userData"]["cursor"]) {
            //     return true;
            // }
            return false;
        });

        return hasevent;
    }

    // 判断对象上是否有鼠标样式
    hasCursor(object?: Object3D) {
        if (!object) {
            return false;
        }
        // 如果 启用事件为关闭的话就不执行事件
        if (object["userData"] && object["userData"]["enableEvent"] === false) {
            return false;
        }
        if (object["userData"] && object["userData"]["cursor"]) {
            return true;
        }
    }

    // 分发鼠标拾取事件
    dispatchEvent() {
        // 提取有事件的对象
        const meshs: Object3D[] = [];

        this._scene.traverseVisible((mesh) => {
            // 判断对象是否有指定的事件
            if (
                this.hasCursor(mesh) ||
                this.hasEvent(mesh, [
                    "leftclick",
                    "rightclick",
                    "click",
                    "dbclick",
                    "mouseenter",
                    "mouseleave",
                    "mousemove",
                    "mousedown",
                    "mouseup",
                ])
            ) {
                if (
                    (mesh as Mesh).isMesh &&
                    !(mesh as Mesh).geometry.boundsTree &&
                    (mesh as Mesh).geometry.attributes.position &&
                    (mesh as Mesh).geometry.attributes.position.count > 0
                ) {
                    computeBoundsTree.apply((mesh as Mesh).geometry);
                    //   disposeBoundsTree.apply((mesh as Mesh).geometry);
                }

                meshs.push(mesh);
            }
        });

        this._object3ds = meshs;

        // 没有鼠标事件就不往下执行了
        if (!this._mousemoveEvent || !this._mouseInRenderer) {
            return;
        }
        // 拾取模型
        let intersection = this._getSelected(meshs);

        if (intersection) {
            const { face, object } = intersection;
            // face && face.normal.applyMatrix4(object.matrixWorld);
            const MouseEvent: ThreeMouseEvent = Object.assign(intersection, {
                // 将来可能用到的对象
                e: this._mousemoveEvent,
                renderer: this._renderer,
                camera: this._camera,
                object: intersection.object,
                type: this._mousemoveEvent.type,
                offsetX: this._mousemoveEvent.offsetX,
                offsetY: this._mousemoveEvent.offsetY,
                x: this._mousemoveEvent.x,
                y: this._mousemoveEvent.y,
            });

            // 触发滑动事件
            if (this._mesh_on === MouseEvent.object) {
                if (this.hasEvent(MouseEvent.object, ["mousemove"])) {
                    MouseEvent.type = "mousemove";
                    MouseEvent.object.dispatchEvent(MouseEvent as never);
                }

                // 鼠标划入滑出事件
            } else if (this._mesh_on !== MouseEvent.object) {
                if (this.hasEvent(this._mesh_on, ["mouseleave"])) {
                    MouseEvent.type = "mouseleave";
                    this._mesh_on!.dispatchEvent(MouseEvent as never);
                }

                if (this.hasEvent(MouseEvent.object, ["mouseenter"])) {
                    MouseEvent.type = "mouseenter";
                    MouseEvent.object.dispatchEvent(MouseEvent as never);
                }
            }

            this._mesh_on = MouseEvent.object || undefined; // 设置为当前鼠标上的对象

            // 鼠标样式
            if (
                this._mesh_on &&
                this._mesh_on.userData &&
                this._mesh_on.userData.cursor
            ) {
                this._renderer.domElement.style.cursor = this._mesh_on.userData.cursor;
            } else {
                this._renderer.domElement.style.cursor = "auto";
            }
        } else {
            if (this.hasEvent(this._mesh_on, ["mouseleave"])) {
                // 鼠标离开事件
                this._mesh_on!.dispatchEvent({
                    e: this._mouseEvent,
                    renderer: this._renderer,
                    camera: this._camera,
                    object: this._mesh_on as Object3D,
                    type: "mouseleave",
                    offsetX: this._mousemoveEvent.offsetX,
                    offsetY: this._mousemoveEvent.offsetY,
                    x: this._mousemoveEvent.x,
                    y: this._mousemoveEvent.y,
                } as never);
            }

            // 置空当前鼠标上面的对象
            this._mesh_on = undefined;

            this._renderer.domElement.style.cursor = "auto"; // 鼠标样式
        }
    }

    // 鼠标离开事件
    _mouseleave = (e: MouseEvent) => {
        this._mouseEvent = e;
        this._mouseInRenderer = false;

        // 鼠标离开事件
        if (this._mousemoveEvent && this.hasEvent(this._mesh_on, ["mouseleave"])) {
            this._mesh_on!.dispatchEvent({
                e: this._mouseEvent,
                renderer: this._renderer,
                camera: this._camera,
                object: this._mesh_on as Object3D,
                type: "mouseleave",
                offsetX: this._mousemoveEvent.offsetX,
                offsetY: this._mousemoveEvent.offsetY,
                x: this._mousemoveEvent.x,
                y: this._mousemoveEvent.y,
            } as never);
        }

        // 置空当前鼠标上面的对象
        this._mesh_on = undefined;

        this._renderer.domElement.style.cursor = "auto"; // 鼠标样式
    };

    // 鼠标滑动 设置鼠标位置
    _mousemove = (e: MouseEvent) => {
        this._mousemoveEvent = e;

        this._frame = this._renderer.info.render.frame;

        if (document.pointerLockElement) {
            // 如果当前有元素 锁住了鼠标

            this._mosePosition.set(0, 0); // 鼠标就指向canvas 的正中心
        } else {
            this._mosePosition.x = (e.offsetX / this._renderer.domElement.offsetWidth) * 2 - 1;
            this._mosePosition.y = -(e.offsetY / this._renderer.domElement.offsetHeight) * 2 + 1;
        }

        this._raycaster.setFromCamera(this._mosePosition, this._camera); // 设置射线拾取的参数
    };

    // 鼠标按下事件
    _mousedown = (e: MouseEvent) => {
        this._mouseEvent = e;
        // 记录按下的位置
        this._mouseDownPosition.set(e.offsetX, e.offsetY);

        this._mousemove(e);

        let intersection = this._getSelected(this._object3ds);

        if (!intersection || !this._mousemoveEvent) {
            return;
        }

        this._mesh_Down = this._mesh_on = intersection.object as Object3D;

        // 触发鼠标按下事件
        if (this.hasEvent(this._mesh_Down, ["mousedown"])) {
            this._mesh_Down.dispatchEvent({
                e: this._mouseEvent,
                ...intersection,
                renderer: this._renderer,
                camera: this._camera,
                object: intersection.object as Object3D,
                type: "mousedown",
                offsetX: this._mousemoveEvent.offsetX,
                offsetY: this._mousemoveEvent.offsetY,
                x: this._mousemoveEvent.x,
                y: this._mousemoveEvent.y,
            } as never);
        }
    };

    // 鼠标弹起事件
    _mouseup = debounce(async (e: PointerEvent) => {
        this._mouseEvent = e;
        // 提取有事件的对象
        const meshs: Object3D[] = [];

        let intersection = this._getSelected(this._object3ds);

        if (!intersection || !this._mousemoveEvent) {
            return;
        }

        this._mesh_on = intersection.object as Object3D;

        if (this.hasEvent(this._mesh_on, ["mouseup"])) {
            this._mesh_on.dispatchEvent({
                e: this._mouseEvent,
                ...intersection,
                renderer: this._renderer,
                camera: this._camera,
                object: intersection.object as Object3D,
                type: "mouseup",
                offsetX: this._mousemoveEvent.offsetX,
                offsetY: this._mousemoveEvent.offsetY,
                x: this._mousemoveEvent.x,
                y: this._mousemoveEvent.y,
            } as never);
        }

        // 触发鼠标弹起事件
        if (
            this._mesh_Down &&
            this._mesh_Down === this._mesh_on &&
            Math.abs(this._mouseDownPosition.x - e.offsetX) < 5 &&
            Math.abs(this._mouseDownPosition.y - e.offsetY) < 5
        ) {
            // 是否有双击事件
            if (this.hasEvent(this._mesh_Down, ["dbclick"])) {
                this._click_num++;

                await new Promise((resolve) => setTimeout(resolve, 200));

                // 只点击了一次
                if (this._click_num === 1) {
                    if (this.hasEvent(this._mesh_Down, ["click"])) {
                        this._mesh_Down.dispatchEvent({
                            e: this._mouseEvent,
                            ...intersection,
                            renderer: this._renderer,
                            camera: this._camera,
                            object: intersection.object as Object3D,
                            type: "click",
                            offsetX: this._mousemoveEvent.offsetX,
                            offsetY: this._mousemoveEvent.offsetY,
                            x: this._mousemoveEvent.x,
                            y: this._mousemoveEvent.y,
                        } as never);
                    }

                    if (this.hasEvent(this._mesh_Down, ["leftclick"]) && e.button === 0) {
                        this._mesh_Down.dispatchEvent({
                            e: this._mouseEvent,
                            ...intersection,
                            renderer: this._renderer,
                            camera: this._camera,
                            object: intersection.object as Object3D,
                            type: "leftclick",
                            offsetX: this._mousemoveEvent.offsetX,
                            offsetY: this._mousemoveEvent.offsetY,
                            x: this._mousemoveEvent.x,
                            y: this._mousemoveEvent.y,
                        } as never);
                    }

                    if (
                        this.hasEvent(this._mesh_Down, ["rightclick"]) &&
                        e.button === 2
                    ) {
                        this._mesh_Down.dispatchEvent({
                            e: this._mouseEvent,
                            ...intersection,
                            renderer: this._renderer,
                            camera: this._camera,
                            object: intersection.object as Object3D,
                            type: "rightclick",
                            offsetX: this._mousemoveEvent.offsetX,
                            offsetY: this._mousemoveEvent.offsetY,
                            x: this._mousemoveEvent.x,
                            y: this._mousemoveEvent.y,
                        } as never);
                    }
                } else if (this._click_num > 1) {
                    if (
                        this.hasEvent(this._mesh_Down, ["dbclick"]) &&
                        new Date().valueOf() - this._click_num > 1 &&
                        e.button === 0
                    ) {
                        this._mesh_Down.dispatchEvent({
                            e: this._mouseEvent,
                            ...intersection,
                            renderer: this._renderer,
                            camera: this._camera,
                            object: intersection.object as Object3D,
                            type: "dbclick",
                            offsetX: this._mousemoveEvent.offsetX,
                            offsetY: this._mousemoveEvent.offsetY,
                            x: this._mousemoveEvent.x,
                            y: this._mousemoveEvent.y,
                        } as never);
                    }
                }
            } else {
                if (this.hasEvent(this._mesh_Down, ["click"])) {
                    this._mesh_Down.dispatchEvent({
                        e: this._mouseEvent,
                        ...intersection,
                        renderer: this._renderer,
                        camera: this._camera,
                        object: intersection.object as Object3D,
                        type: "click",
                        offsetX: this._mousemoveEvent.offsetX,
                        offsetY: this._mousemoveEvent.offsetY,
                        x: this._mousemoveEvent.x,
                        y: this._mousemoveEvent.y,
                    } as never);
                }

                if (this.hasEvent(this._mesh_Down, ["leftclick"]) && e.button === 0) {
                    this._mesh_Down.dispatchEvent({
                        e: this._mouseEvent,
                        ...intersection,
                        renderer: this._renderer,
                        camera: this._camera,
                        object: intersection.object as Object3D,
                        type: "leftclick",
                        offsetX: this._mousemoveEvent.offsetX,
                        offsetY: this._mousemoveEvent.offsetY,
                        x: this._mousemoveEvent.x,
                        y: this._mousemoveEvent.y,
                    } as never);
                }

                if (this.hasEvent(this._mesh_Down, ["rightclick"]) && e.button === 2) {
                    this._mesh_Down.dispatchEvent({
                        e: this._mouseEvent,
                        ...intersection,
                        renderer: this._renderer,
                        camera: this._camera,
                        object: intersection.object as Object3D,
                        type: "rightclick",
                        offsetX: this._mousemoveEvent.offsetX,
                        offsetY: this._mousemoveEvent.offsetY,
                        x: this._mousemoveEvent.x,
                        y: this._mousemoveEvent.y,
                    } as never);
                }
            }

            this._click_num = 0;
        }
    });
}
